package com.powerbank.gateway.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * JWT认证全局过滤器
 * 支持JWT认证、黑名单检查、频率限制等功能
 */
@Slf4j
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Value("${powerbank.gateway.auth.jwt-secret}")
    private String jwtSecret;

    @Value("#{@gatewayConfig.getWhiteList()}")
    private List<String> skipAuthPaths;
    
    private final StringRedisTemplate redisTemplate;
    
    public AuthGlobalFilter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String BEARER_PREFIX = "Bearer ";
    private static final String TOKEN_BLACKLIST_PREFIX = "token:blacklist:";
    private static final String USER_REQUEST_COUNT_PREFIX = "user:request:count:";
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String method = request.getMethod().name();
        String clientIp = getClientIpAddress(request);

        log.debug("请求路径: {}, 方法: {}, 客户端IP: {}", path, method, clientIp);

        // 检查是否跳过认证
        if (isSkipAuth(path)) {
            log.debug("跳过认证: {}", path);
            return chain.filter(exchange);
        }

        // 获取Authorization头
        String authHeader = request.getHeaders().getFirst(AUTHORIZATION_HEADER);
        
        if (StrUtil.isEmpty(authHeader) || !authHeader.startsWith(BEARER_PREFIX)) {
            log.warn("缺少或无效的Authorization头: {}", authHeader);
            return unauthorized(exchange.getResponse(), "缺少认证信息");
        }

        // 提取Token
        String token = authHeader.substring(BEARER_PREFIX.length());
        
        try {
            // 检查Token黑名单
            if (isTokenBlacklisted(token)) {
                log.warn("Token已被列入黑名单");
                return unauthorized(exchange.getResponse(), "Token已失效");
            }
            
            // 验证Token
            Claims claims = validateToken(token);
            
            String userId = claims.get("userId").toString();
            String username = claims.get("username").toString();
            String userType = claims.get("userType", String.class);
            
            // 检查用户请求频率
            if (isRateLimited(userId)) {
                log.warn("用户 {} 请求过于频繁", userId);
                return tooManyRequests(exchange.getResponse(), "请求过于频繁，请稍后重试");
            }
            
            // 记录用户请求
            recordUserRequest(userId);
            
            // 将用户信息添加到请求头
            ServerHttpRequest newRequest = request.mutate()
                .header("X-User-Id", userId)
                .header("X-Username", username)
                .header("X-User-Type", userType)
                .header("X-Client-IP", clientIp)
                .header("X-Request-Time", String.valueOf(System.currentTimeMillis()))
                .build();

            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            
            log.debug("认证成功，用户ID: {}, 用户类型: {}", userId, userType);
            return chain.filter(newExchange);
            
        } catch (Exception e) {
            log.warn("Token验证失败: {}", e.getMessage());
            return unauthorized(exchange.getResponse(), "认证失败");
        }
    }

    /**
     * 检查是否跳过认证
     */
    private boolean isSkipAuth(String path) {
        return skipAuthPaths.stream().anyMatch(skipPath -> {
            if (skipPath.endsWith("/**")) {
                String prefix = skipPath.substring(0, skipPath.length() - 3);
                return path.startsWith(prefix);
            } else {
                return path.equals(skipPath);
            }
        });
    }
    
    /**
     * 获取客户端真实IP地址
     */
    private String getClientIpAddress(ServerHttpRequest request) {
        String clientIp = request.getHeaders().getFirst("X-Forwarded-For");
        if (StrUtil.isEmpty(clientIp) || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getHeaders().getFirst("X-Real-IP");
        }
        if (StrUtil.isEmpty(clientIp) || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getHeaders().getFirst("Proxy-Client-IP");
        }
        if (StrUtil.isEmpty(clientIp) || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getHeaders().getFirst("WL-Proxy-Client-IP");
        }
        if (StrUtil.isEmpty(clientIp) || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getRemoteAddress() != null ? 
                      request.getRemoteAddress().getAddress().getHostAddress() : "";
        }
        
        // 处理多个IP的情况，取第一个
        if (StrUtil.isNotEmpty(clientIp) && clientIp.contains(",")) {
            clientIp = clientIp.split(",")[0].trim();
        }
        
        return clientIp;
    }
    
    /**
     * 检查Token是否在黑名单中
     */
    private boolean isTokenBlacklisted(String token) {
        try {
            String key = TOKEN_BLACKLIST_PREFIX + token;
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (Exception e) {
            log.warn("检查Token黑名单失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 检查用户请求是否被限流
     */
    private boolean isRateLimited(String userId) {
        try {
            String key = USER_REQUEST_COUNT_PREFIX + userId;
            String count = redisTemplate.opsForValue().get(key);
            return count != null && Integer.parseInt(count) > 100; // 每分钟最多100次请求
        } catch (Exception e) {
            log.warn("检查用户请求频率失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 记录用户请求次数
     */
    private void recordUserRequest(String userId) {
        try {
            String key = USER_REQUEST_COUNT_PREFIX + userId;
            redisTemplate.opsForValue().increment(key);
            redisTemplate.expire(key, 1, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.warn("记录用户请求次数失败: {}", e.getMessage());
        }
    }

    /**
     * 验证JWT Token
     */
    private Claims validateToken(String token) {
        SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 返回请求过于频繁响应
     */
    private Mono<Void> tooManyRequests(ServerHttpResponse response, String message) {
        response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        Map<String, Object> result = new HashMap<>();
        result.put("code", 429);
        result.put("message", message);
        result.put("data", null);

        String body = JSONUtil.toJsonStr(result);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 返回未授权响应
     */
    private Mono<Void> unauthorized(ServerHttpResponse response, String message) {
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        Map<String, Object> result = new HashMap<>();
        result.put("code", 401);
        result.put("message", message);
        result.put("data", null);

        String body = JSONUtil.toJsonStr(result);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -100;
    }
}